Type Inference

Cyclone allows many explicit types to be elided. In short, you write _ (underscore) where a type should be and the compiler tries to figure out the type for you. Type inference can make C-like Cyclone code easier to write and more readable. For example,

  _ x = malloc(sizeof(sometype_t));

is a fine substitute for

  sometype_t * x = malloc(sizeof(sometype_t));

Of course, explicit types can make code more readable, so it is often better style not to use inference.

Inference is even more useful because of Cyclone’s advanced typing constructs. For example, it is much easier to write down _ than a type for a function pointer.

We now give a rough idea of when you can elide types and how types get inferred. In practice, you tend to develop a sense of which idioms succeed, and, if there’s a strange compiler-error message about a variable’s type, you give more explicit information about the variable’s type.

Syntax

As far as the parser is concerned, _ is a legal type specifier. However, the type-checker will immediately reject _ in these places (or at least it should):

Note that _ can be used for part of a type. A silly example is $(_,int) = $(3,4); a more useful example is an explicit cast to a non-nullable pointer (to avoid a compiler warning). For example:

  void f(some_big_type * x, some_big_type @ y) {
  if(x != NULL) {
    y = (_ *@notnull) x;
  }
Semantics

Except for the subtleties discussed below, using _ should not change the meaning of programs. However, it may cause a program not to type-check because the compiler no longer has the type information it needs at some point in the program. For example, the compiler rejects x->f if it does not know the type of x because the different struct types can have members named f.

The compiler infers the types of expressions based on uses. For example, consider:

  _ x = NULL;
  x = g();
  x->f;

This code will type-check provided the return type of g is a pointer to a struct with a field named f. If the two statements were in the other order, the code would not type-check. Also, if g returned an int, the code would not type-check, even without the x->f expression, because the _ x = NULL constrains x to have a pointer type.

However, the above discussion assumes that sequences of statements are type-checked in order. This is true, but in general the type-checker’s order is unspecified.

Subtleties

In general, inference has subtle interactions with implicit coercions (such as from t *@notnull to t *@nullable) and constants that have multiple types (such as numeric constants).

The following is a desirable property: If a program is modified by replacing some explicit types with _ and the program still type-checks, then its meaning is the same. This property does not hold! Here are two examples:

Numeric Types

This program prints -24 1000:

  int f() {
  char c = 1000;
  return c;
  }
  int g() {
  _ c = 1000; // compiler infers int
  return c;
  }
  int main() {
  printf("%d %d", f(), g());
  return 0;
  }
Order Matters

Here is an example where the function’s meaning depends on the order the type-checker examines the function:

  void h1(int *@notnull c, int maybe) {
    _ a;
    if(maybe)
      a = c;
    else
      a = NULL;
  }

At first, the type of a is completely unconstrained. If we next consider a = c, we will give a the type of c, namely int *@notnull, an int pointer that cannot be NULL. Clearly that makes the assignment a = NULL problematic, but Cyclone allows assignment from nullable pointers to non-nullable pointers; it gives a compile-time warning and inserts a run-time check that the value is not NULL. Here the check will fail and an exception will be raised. That is, h1(p,0) is guaranteed to raise an exception.

But what if the type-checker examines a = NULL first? Then the type-checker will constrain a’s type to be a nullable pointer to an unconstrained type. Then the assignment a = c will constrain that type to be int, so the type of a is int *. An assignment from int *@notnull to int * is safe, so there is no warning. Moreover, the assignment a = NULL is not a run-time error.

The order of type-checking is left unspecified. In the future, we intend to move to a system that is order-independent.